57 — 调试 R3F 应用
简介
我们一直在使用 React 和 React Three Fiber,但还没有真正讨论过监控和调试。
这涉及 React 和 React Three Fiber 两者,而且让我们的开发生活尽可能轻松很重要,这样我们就能专注于应用本身,而不是专注于调试,也不是去弄明白为什么帧率会突然下降。
我们会先讨论 React 调试,然后聚焦于 React Three Fiber 的调试和监控。
准备工作
这个初始项目和之前的初始项目非常相似,包含一个球体、一个立方体和一个绿色的地面。
@react-three/drei 依赖已经安装在项目中,我们使用 OrbitControls 辅助工具来移动相机。
StrictMode
有一个重要的 React 特性,为了简化,我们之前一直没有使用,但你应该在所有 React 项目中都添加它,那就是 StrictMode。
StrictMode 会就你的应用中潜在的问题向你发出警告。
以下是一些例子:
- 未使用的 import
- 无限渲染循环
- 忘记的
useEffect依赖 - 已废弃的做法
- 等等。
把它添加到你的应用中,只需在它外面包一个标签即可。
在 /src/index.jsx 中,从 react 导入 StrictMode:
import { StrictMode } from 'react' 用一个 <StrictMode> 包裹 <Canvas>:
root.render(
<StrictMode>
<Canvas
camera={ {
fov: 45,
near: 0.1,
far: 50,
position: [ - 4, 3, 6 ]
} }
>
<Experience />
</Canvas>
</StrictMode>
) 就这样。
潜在的错误现在会在终端和控制台中显示为警告。
在接下来的课程中,我们不应该遇到特定的错误,这就是我们不打算使用它的原因,但当你开始自己探索时,应该保留 StrictMode。
不用担心 StrictMode 在生产环境中的影响。应用构建完成后它就会被忽略。
浏览器扩展
我们可以安装一个名为 React Developer Tools 的扩展,它能帮助我们实时检查和修改 component。
Chrome 和 Firefox 都可以使用
把它添加到你的浏览器中。
关闭并重新打开你的 React 应用对应的标签页,然后在开发者工具中查看 ⚛️ Components 标签(我们不打算讨论 ⚛️ Profile 标签)。
在这里,你会找到一个 component 树,并且能够实时调整它们的 props 和 state。
注意,R3F 元素(<mesh> 或 <meshBasicMaterial>)不会出现在这个树中。
不过,如果我们创建自己的包含 R3F 的 component,就可以从这里调试它们的 props 和 state。
使用 Leva 的调试 UI
到目前为止,我们还没有使用过任何调试 UI,尽管用它来调整那些属性(比如 <Text> 或 <MeshReflectorMaterial> 的)会很有用。
我们可以使用经典的 lil-gui 或任何其他替代品,比如 TweakPane、control-panel、Guifiy 等等。
但有一个由 React 实现的新选择,名为 Leva。
在我编写和录制这些课程时,Leva 仍处于 PMNDRS 团队的“密集”开发中,可能会有所变化,但和之前的课程一样,我们会强制指定版本以避免任何意外。
安装
在终端中,使用 npm install leva@0.9.34 安装 leva(我们强制指定版本以防止意外,你可以忽略潜在的安全漏洞警告)。
把它添加到我们的项目中,只需添加一个 hook 即可。
在 Experience.jsx 中,从 leva 导入 useControls:
import { useControls } from 'leva' 我们是在 Experience 中这样做的,但也可以在任何其他 component 中做,甚至可以同时在多个 component 中做。
第一个 tweak
首先,我们要创建一个 tweak,让我们可以改变球体的位置。
在 Experience 中,按如下方式调用 useControls:
export default function Experience()
{
const controls = useControls({ position: - 2 })
console.log(controls.position)
// ...
}
如你所见,调试 UI 自动出现在右上角。
我们可以移动它、缩小它,甚至可以搜索特定的 tweak。
如果你直接在 tweak 中改变值,你会看到控制台用新的值再次打印日志。这意味着 component 正在被重新渲染。
虽然这是应该避免的,但它并不一定是坏事,而在这里它是合理的,因为确实有一个值发生了变化。
另外,React 和 R3F 知道什么发生了变化,只会更新严格必要的内容。
在使用 position 值之前,我们可以对它进行解构,以便只获取 position:
export default function Experience()
{
const { position } = useControls({ position: - 2 })
console.log(position)
// ...
} 现在我们可以把它用在橙色球体 <mesh> 的 position-x 属性上:
<mesh position-x={ position }>
如果你改变 Tweak 值,你会看到球体的位置发生变化。
范围
不错,但不太方便。与其必须写一个值,我们更希望有一个范围。
这可以通过向 useControls 的 position 传递一个带有特定属性的对象来实现:
const { position } = useControls({
position:
{
value: - 2,
min: - 4,
max: 4,
step: 0.01
}
})
现在我们可以拖动滑块来改变值。
向量
好多了。但如果我们想在 y 轴上做同样的事呢?
与其添加第二个范围 tweak,我们将使用一个 vector 2 tweak:
const { position } = useControls({
position:
{
value: { x: - 2, y: 0 },
step: 0.01
}
}) 别忘了更新 <mesh> component:
<mesh position={ [ position.x, position.y, 0 ] }>
现在我们可以用某种摇杆来移动它。
但我们能更进一步吗?
在 useControls 中添加一个 z 值:
const { position } = useControls({
position:
{
value: { x: - 2, y: 0, z: 0 },
step: 0.01
}
}) 并相应地更新 <mesh>:
<mesh position={ [ position.x, position.y, position.z ] }>
如你所见,是的,它能用,但我们失去了摇杆,这很合理,因为 3D 摇杆不太方便。
太可惜了,让我们回到一个 vector 2 tweak:
const { position } = useControls({
position:
{
value: { x: - 2, y: 0 },
step: 0.01
}
}) 然后更新 <mesh>:
<mesh position={ [ position.x, position.y, 0 ] }> 你可能已经注意到 y 轴似乎是反的。
我们可以通过向 tweak 添加 joystick: 'invertY' 来解决这个问题:
const { position } = useControls({
position:
{
value: { x: - 2, y: 0 },
step: 0.01,
joystick: 'invertY'
}
})
摇杆的范围是有限的,但把光标贴到边缘可以让值超出初始范围。
颜色
我们可以通过传递一个看起来像颜色的字符串来使用颜色。
在 useControls 中添加一个 color 属性:
const { position } = useControls({
// ...
color: '#ff0000'
}) 添加 tweak 时,别忘了在解构中把它取为一个变量:
const { position, color } = useControls({
// ...
}) 并把它添加到球体的 <meshStandardMaterial>:
<meshStandardMaterial color={ color } />
我们可以使用各种颜色格式:
'rgb(255, 0, 0)''orange''hsl(100deg, 100%, 50%)''hsla(100deg, 100%, 50%, 0.5)'{ r: 200, g: 106, b: 125, a: 0.4 }
注意,这样表达颜色时 alpha 通道不会生效,因为 Three.js 通过 opacity 和 transparent 属性来处理 alpha。
Leva 会保留输入格式,而 Three.js 也以同样的方式支持它们。
布尔值
向 useControls 添加一个 visible 属性并解构它:
const { position, color, visible } = useControls({
// ...
visible: true
}) 在球体 <mesh> 上把它用于其 visible 属性:
<mesh visible={ visible } position={ [ position.x, position.y, 0 ] }>
现在我们可以切换球体的可见性。
区间
虽然我们不会在场景中使用它,但我们可以设置一个区间,它类似于范围,但有两个光标(不需要解构它):
const { position, color, visible } = useControls({
// ...
myInterval:
{
min: 0,
max: 10,
value: [4, 5],
}
})
按钮
同样,我们不会在场景中使用它,但我们可以创建一个按钮,点击后会调用一个函数。
为此,我们需要从 leva 导入 button:
import { button, useControls } from 'leva' 把它作为该属性的值调用,并把要调用的函数作为参数传给它:
const { position, color, visible } = useControls('sphere', {
// ...
clickMe: button(() => { console.log('ok') })
})
选择
我们可以通过在 options 属性上设置一个数组来创建一个选择输入:
const { position, color, visible } = useControls('sphere', {
// ...
choice: { options: [ 'a', 'b', 'c' ] }
})
文件夹
和 lil-gui 类似,如果太多 tweak 让事情变得有点太复杂,我们可以把它们组织到文件夹中。
为此,把字符串作为 useControls 的第一个参数传入:
const { position, color, visible } = useControls('sphere', {
position:
{
value: { x: - 2, y: 0 },
step: 0.01,
joystick: 'invertY'
},
color: 'orange',
visible: true
})
要创建另一个文件夹,再次调用 useControls,但使用不同的名称,并在其中放入你想要的任何内容,比如一个 scale(虽然我们没有把它和 cube 关联起来,但你可以自己试着做一下):
const { scale } = useControls('cube', {
scale:
{
value: 1.5,
step: 0.01,
min: 0,
max: 5
}
})
如果你需要在文件夹内再创建一个文件夹,可以使用可以从 leva 导入的 folder 函数来实现,但我们这里不会涉及。
配置
最后,我们可以配置 Leva。
首先,我们需要自己把这个 component 添加到应用中,以便可以调整它的属性。
我们不能把它添加到 Experience 中,因为它位于 <Canvas> 内,而 <Canvas> 内的一切都是为 R3F 准备的。相反,我们需要把它添加到 index.jsx 文件中 <Canvas> 之外。
在 index.jsx 中,从 leva 导入 Leva:
import { Leva } from 'leva' 把它添加到 <Canvas> 之外:
root.render(
<StrictMode>
<Leva />
<Canvas
camera={ {
fov: 45,
near: 0.1,
far: 50,
position: [ - 4, 3, 6 ]
} }
>
<Experience />
</Canvas>
</StrictMode>
) 现在我们可以添加像 collapsed 这样的属性:
<Leva collapsed />
查看文档以了解更多属性 https://github.com/pmndrs/leva/blob/main/docs/configuration.md
如果你想要更多示例 https://codesandbox.io/examples/package/leva
使用 r3f-perf 进行监控
说到监控,我们仍然可以使用传统的解决方案,比如 Stats.js,但 Renaud Rohlinger(@onirenaud)开发了一个专门为 R3F 打造的更先进的解决方案,名为 R3F-Perf。
在你的终端中,使用 npm install r3f-perf@7.2 添加 r3f-perf 依赖(我们强制指定版本以防止意外,你可以忽略潜在的安全漏洞警告)。
在 Experience.jsx 中,从 r3f-perf 导入 Perf:
import { Perf } from 'r3f-perf' 并把它添加到你的 JSX 中:
export default function Experience()
{
// ...
return <>
<Perf />
{/* ... */}
</>
}
默认情况下,界面位于右上角,会与 Leva 冲突,但我们可以用 position 属性来改变它:
<Perf position="top-left" />
R3F-Perf 会显示数量惊人的有用信息。
我们甚至能访问绘制调用次数、内存使用量、渲染场景所花的时间等等。
为了让事情更有趣一点,我们可以给 Leva 添加一个 perfVisible 复选框:
const { perfVisible } = useControls({
perfVisible: true
}) 并让 <Perf> 具有条件性:
{ perfVisible && <Perf position="top-left" /> }
这样,我们就可以从我们的调试 UI 中切换它
查看文档以了解更多关于 R3F-Perf 的信息 https://github.com/utsuboco/r3f-perf